/*
 * sysmo 服务器监控项基类
 * Copyright (C) 2009  YuLicheng
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sysmo.server.node.item;

import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import org.sysmo.db.DatabaseInterface;
import org.sysmo.db.DbGraph;
import org.sysmo.db.DbItem;
import org.sysmo.db.DbWarning;
import org.sysmo.server.node.item.graphgroup.GraphGroup;
import org.sysmo.server.node.item.graphgroup.GraphGroupExistsException;
import org.sysmo.server.node.item.graphgroup.GraphGroupNotExistsException;
import org.sysmo.server.util.ChildEventListener;
import org.sysmo.server.util.InstanceNotInitializedException;

/**
 *
 * 监控项对象。此类可被实例化为为没有连接客户端的监控项。
 * <br />
 *
 * 被监控节点上可监控的内容按照层次结构分为监控项（项）、图表和监控值（值）。一个客户
 * 端节点可包含多个监控项，一个监控项可包含多个图表，一个图表中可包含多个监控值。
 * <br />
 *
 * 其中一个监控项包含一个数据采集脚本、一个数据验证脚本，这两个脚本负责采集和验证此项
 * 中的所有图表的所有监控值。一个图表即单张图表图片，属于此图表的所有监控值会以用户指
 * 定的方式叠加绘制到同一张图片上。监控值是最小记录单位。
 * <br />
 *
 * 监控项分为已与客户端连接的监控项和没有和客户端连接的监控项，其中已与客户端连接的监
 * 控项可以进行远程配置，没有连接的监控项则只能查看数据记录，但是仍然可以对监控项中的
 * 图表进行配置，因为这些配置不需要涉及与客户端的通信。
 * <br />
 * 
 * 此类中的{@link #sync()}方法将所有修改一次性写入数据库，此方法由
 * {@link ConnectedItem}实例调用。
 * <br />
 *
 * 注意：客户端和用户使用监控项名、图表名和监控值名来唯一确定一个监控项，因此这些项目
 * 在命名时不能与同一层次中现有的项目同名。但是系统内部是使用数据库中配置的ID来识别
 * 的。因此用户在设定名称后仍然可以随意修改（只要不重名），然后需要更新客户端使用的脚
 * 本文件的输出以反映相关的修改。服务器在接收到客户端发送的监控记录时将会重新将相应的
 * 名称映射到ID上。
 *
 * @author YuLicheng
 * @see GraphGroup
 * @see ConnectedItem
 */
public class Item implements ChildEventListener<GraphGroup> {

    /**
     * 数据库访问接口。
     */
    protected DatabaseInterface dbInterface;
    /**
     * 此监控项ID。
     */
    protected DbItem item;
    /**
     * 属于此监控项的图表列表。
     */
    protected HashMap<Integer, GraphGroup> allGraphs =
            new HashMap<Integer, GraphGroup>();
    /**
     * 子对象事件接口。
     */
    protected ChildEventListener<Item> eventListener;

    /**
     * 此构造方法创建一个未连接客户端的监控项实例。此监控项必须已存在。此方法会从数据
     * 库中读取此监控项下的所有图表对象信息。
     *
     * @param eventListener 子对象事件接口。
     * @param dbInterface 数据库访问接口。
     * @param id 监控项ID。
     * @throws ItemNotExistsException ID不存在。
     */
    public Item(ChildEventListener<Item> eventListener, 
            DatabaseInterface dbInterface, int id) throws ItemNotExistsException {
        this.eventListener = eventListener;
        this.dbInterface = dbInterface;
        try {
            this.item = dbInterface.query_ItemTable(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (item == null) {
            throw new ItemNotExistsException(id);
        }
        Vector<DbGraph> graphs = dbInterface.query_GraphTableOfItem(item.iid);
        for (DbGraph graph : graphs) {
            try {
                allGraphs.put(graph.gid, new GraphGroup(this, dbInterface,
                        graph.gid));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 此构造方法只由子类调用。
     *
     * @param eventListener 子对象事件接口。
     * @param dbInterface 数据库访问接口。
     */
    protected Item(ChildEventListener<Item> eventListener, 
            DatabaseInterface dbInterface) {
        this.eventListener = eventListener;
        this.dbInterface = dbInterface;
    }

    /**
     * 将此对象设为无效，并且此对象的所有子对象也将无效，此方法不会从数据库删除任何
     * 数据。
     */
    public void invalid() {
        synchronized (this) {
            for (GraphGroup graph : allGraphs.values()) {
                graph.invalid();
            }
            allGraphs.clear();
        }
        item = null;
    }

    /**
     * 删除此监控项。这将同时删除其中的所有图表、相应图表中的所有监控值，以及这些监控
     * 值的所有历史记录。另外在删除后还会调用{@link #readyToRemove()}方法，已连接
     * 客户端的子类可覆盖此方法来实现发送删除消息到客户端，此时客户端会将相应监控项的
     * 所有配置数据一并删除。
     *
     * @see #readyToRemove()
     */
    public void remove() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        readyToRemove();
        eventListener.childRemoved(this);
        try {
            dbInterface.delete_ItemTable(item.iid);
        } catch (Exception e) {
            e.printStackTrace();
        }
        invalid();
    }

    /**
     * 当此监控项被删除前，会调用此方法。{@link ConnectedItem}子类应该覆盖此方法以
     * 发送消息给客户端。本类中此方法没有任何作用。
     *
     * @see #remove()
     */
    protected void readyToRemove() {
        //Empty Stub
    }

    /**
     * 获取监控项名称。
     *
     * @return 此监控项名称。
     */
    public String getName() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        return item.name;
    }

    /**
     * 返回将客户端产生的警告转发到的联系人信息。
     *
     * @return 联系方式
     */
    public WarningContect getWarningContect() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        WarningContect warningContect = new WarningContect(dbInterface, item.mail);
        return warningContect;
    }

    /**
     * 获取监控项ID。
     *
     * @return 此监控项的ID。
     */
    public int getID() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        return item.iid;
    }

    /**
     * 获得数据采集时间间隔。此值以分钟为单位。
     * 
     * @return 时间间隔。
     */
    public int getRunInterval() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        return item.time_interval;
    }

    /**
     * 获得此监控项的所有警报列表。
     * 
     * @return 所有警告列表。
     */
    public List<Warning> getWarnings() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        //TODO: 查询警告的默认无关ID
        Vector<DbWarning> warnings;
        try {
            warnings = dbInterface.query_WarningTable(0, item.iid, 0, 0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        Vector<Warning> allWarnings = new Vector<Warning>();
        for (DbWarning warning : warnings) {
            allWarnings.add(new Warning(dbInterface, warning));
        }
        return allWarnings;
    }

    /**
     * 添加一个图表。新添加的图表中没有监控值。
     *
     * @param name 图表名。
     * @return 新增的图表。
     * @throws GraphGroupExistsException 若存在同名图表则抛出此异常。
     */
    public GraphGroup addGraph(String name) throws GraphGroupExistsException {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        GraphGroup graph = new GraphGroup(this, dbInterface, name, item.iid);
        synchronized (this) {
            allGraphs.put(graph.getID(), graph);
        }
        return graph;
    }

    /**
     * 用图表ID获取图表实例。
     *
     * @param id 图表ID。
     * @return 图表实例
     * @throws GraphGroupNotExistsException 图表ID不存在。
     */
    public GraphGroup getGraph(int id) throws GraphGroupNotExistsException {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        GraphGroup graph;
        synchronized (this) {
            graph = allGraphs.get(id);
        }
        if (graph == null) {
            throw new GraphGroupNotExistsException(id);
        }
        return graph;
    }

    /**
     * 获取此监控项中的所有图表列表。
     *
     * @return 图表列表。
     */
    public List<GraphGroup> getAllGraphes() {
        if (item == null) {
            throw new InstanceNotInitializedException();
        }
        Vector<GraphGroup> graphs = new Vector<GraphGroup>();
        synchronized (this) {
            graphs.addAll(allGraphs.values());
        }
        return graphs;
    }

    /**
     * 将此实例的元数据修改写入数据库。
     */
    protected void sync() {
        try {
            dbInterface.update_ItemTable(item);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void childRemoved(GraphGroup removedObject) {
        if (removedObject != null) {
            synchronized (this) {
                allGraphs.remove(removedObject.getID());
            }
        }
    }
    
}
